/*
   Copyright 2012 Shane Phillips

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */
package org.jdraw;

import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.event.WindowListener;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import javax.imageio.ImageIO;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.EtchedBorder;

import org.jdraw.swing.TaggedMenuItem;
import org.jdraw.utils.Dialogs;
import org.jdraw.utils.FileExtensionFilters;
import org.jdraw.utils.FileIO;
import org.jdraw.utils.RecentFiles;
import org.jdraw.utils.Dialogs.FileSelectionMode;

public class MainWindow extends JFrame {

	private static final long serialVersionUID = 1L;

	private JPanel contentPane, statusBar;
	private JMenuBar menuBar;
	private JMenu menuFile, menuCanvas, menuRecentFiles, menuHelp;
	private DrawingPanel drawingPanel;
	private JSplitPane splitPane;
	private JTextArea textArea;
	private JLabel statusLabel;
	// File Menu
	private JMenuItem miFileNew, miFileOpen, miFileSave, miFileSaveAs, miClose;
	// Canvas Menu
	private JMenuItem miRefresh, miExportImage;
	// Help Menu
	private JMenuItem miReference;
	JScrollPane panelScrollPane;
	private MenuActionListener menuActionListener;

	private ResourceBundle rb;
	private Locale locale;
	private Logger log;

	private boolean fileModified;
	private File currentFile;
	private String jsCode = "";

	private ScriptEngineManager sem;
	private ScriptEngine se;
	private Canvas canvas;
	private RecentFiles recentFiles;

	private int top, left, width, height, state;
	private final int RECENT_FILE_COUNT = 10;

	/**
	 * Application entry point
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					MainWindow frame = new MainWindow();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Constructor for the MainWindow
	 */
	public MainWindow() {
		setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		loadPreferences();
		setExtendedState(state);
		setBounds(left, top, width, height);

		MainWindowListener windowListener = new MainWindowListener();
		addWindowListener(windowListener);
		addWindowStateListener(windowListener);
		addComponentListener(windowListener);

		// Misc stuff
		locale = Locale.getDefault();
		rb = ResourceBundle.getBundle("resources", locale);
		log = Logger.getLogger(this.getClass().getName());

		// TODO: move this to configuration
		log.setUseParentHandlers(false);
		Handler consoleHandler = new ConsoleHandler();
		consoleHandler.setLevel(Level.FINE);
		log.addHandler(consoleHandler);
		log.setLevel(Level.FINE);

		// SE
		sem = new ScriptEngineManager();
		se = sem.getEngineByExtension("js");
		canvas = new Canvas();
		se.put("canvas", canvas);

		// Components
		menuBar = new JMenuBar();
		setJMenuBar(menuBar);

		menuFile = new JMenu(rb.getString("menu.file"));
		menuBar.add(menuFile);

		miFileNew = new JMenuItem(rb.getString("menu.file.new"));
		miFileNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
				InputEvent.CTRL_MASK));
		menuFile.add(miFileNew);
		miFileOpen = new JMenuItem(rb.getString("menu.file.open"));
		miFileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
				InputEvent.CTRL_MASK));
		menuFile.add(miFileOpen);
		miFileSave = new JMenuItem(rb.getString("menu.file.save"));
		miFileSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
				InputEvent.CTRL_MASK));
		miFileSave.setEnabled(false);
		menuFile.add(miFileSave);

		miFileSaveAs = new JMenuItem(rb.getString("menu.file.saveas"));
		menuFile.add(miFileSaveAs);

		JSeparator sep = new JSeparator();
		menuFile.add(sep);

		menuRecentFiles = new JMenu(rb.getString("menu.file.recent"));
		menuFile.add(menuRecentFiles);

		JSeparator sep2 = new JSeparator();
		menuFile.add(sep2);

		miClose = new JMenuItem(rb.getString("menu.file.close"));
		menuFile.add(miClose);

		menuCanvas = new JMenu(rb.getString("menu.canvas"));
		menuBar.add(menuCanvas);

		miRefresh = new JMenuItem(rb.getString("menu.canvas.refresh"));
		miRefresh.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0));
		menuCanvas.add(miRefresh);

		miExportImage = new JMenuItem(rb.getString("menu.canvas.export"));
		miExportImage.setEnabled(false);
		menuCanvas.add(miExportImage);

		menuHelp = new JMenu(rb.getString("menu.help"));

		miReference = new JMenuItem(rb.getString("menu.help.online"));
		menuHelp.add(miReference);

		menuBar.add(menuHelp);

		contentPane = new JPanel();
		contentPane.setLayout(new BorderLayout(0, 0));
		setContentPane(contentPane);

		statusBar = new JPanel();
		contentPane.add(statusBar, BorderLayout.SOUTH);
		statusBar.setBorder(new EtchedBorder(3));

		splitPane = new JSplitPane();
		splitPane.setResizeWeight(0.8);
		splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
		contentPane.add(splitPane, BorderLayout.CENTER);

		drawingPanel = new DrawingPanel();
		panelScrollPane = new JScrollPane(drawingPanel);
		panelScrollPane
				.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
		panelScrollPane
				.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

		// splitPane.setLeftComponent(drawingPanel);
		splitPane.setLeftComponent(panelScrollPane);

		textArea = new JTextArea();
		Font font = new Font(Font.MONOSPACED, Font.PLAIN, 14);
		textArea.setFont(font);
		textArea.setEditable(true);
		textArea.addKeyListener(new TextAreaListener());
		JScrollPane scrollPane = new JScrollPane(textArea);
		scrollPane
				.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
		splitPane.setRightComponent(scrollPane);

		statusLabel = new JLabel();
		statusBar.add(statusLabel, 0);
		statusLabel.setFont(font);

		setFileModified(false);
		setCurrentFile(null);

		menuActionListener = new MenuActionListener();
		addMenuActionListeners();
		refreshRecentFiles();

		String labelText = String.format(
				rb.getString("label.status.canvas.size"), canvas.getWidth(),
				canvas.getHeight());
		statusLabel.setText(labelText);
	}

	// Getters / Setters
	public boolean isFileModified() {
		return fileModified;
	}

	public void setFileModified(boolean fileModified) {
		if (fileModified == this.fileModified) {
			return;
		}
		this.fileModified = fileModified;
		miFileSave.setEnabled(fileModified);
		miRefresh.setEnabled(true);
		if (fileModified) {
			String title = String.format(rb.getString("window.main.title"),
					rb.getString("document.untitled") + "*");
			if (currentFile != null) {
				title = String.format(rb.getString("window.main.title"),
						currentFile.getAbsolutePath() + "*");
			}

			setTitle(title);
		}
	}

	public File getCurrentFile() {
		return currentFile;
	}

	public void setCurrentFile(File currentFile) {
		this.currentFile = currentFile;
		if (currentFile != null) {
			String title = String.format(rb.getString("window.main.title"),
					currentFile.getAbsolutePath());
			setTitle(title);
			miFileSaveAs.setEnabled(true);
			miRefresh.setEnabled(true);
		} else {
			String title = String.format(rb.getString("window.main.title"),
					rb.getString("document.untitled"));
			setTitle(title);
			miFileSaveAs.setEnabled(false);
			miRefresh.setEnabled(false);
		}
		setFileModified(false);
		jsCode = textArea.getText();
		if (currentFile != null) {
			recentFiles.put(currentFile);
		}
		refreshRecentFiles();
	}

	// Methods

	private void addMenuActionListeners() {
		for (int menuCount = 0; menuCount < menuBar.getMenuCount(); menuCount++) {
			JMenu menu = menuBar.getMenu(menuCount);
			for (int item = 0; item < menu.getItemCount(); item++) {
				if (menu.getItem(item) != null
						&& !TaggedMenuItem.class.isInstance(menu.getItem(item))) {
					menu.getItem(item).addActionListener(menuActionListener);
				}
			}
		}

	}

	private boolean newFile() {
		if (isFileModified()) {
			if (Dialogs.showSavePrompt(rb.getString("prompt.save.changes"))) {
				if (!saveFile(false)) {
					return false;
				}

			}
		}
		setCurrentFile(null);
		textArea.setText(null);
		jsCode = "";
		refreshCanvas();
		miExportImage.setEnabled(false);
		return true;
	}

	private boolean saveFile(boolean saveAs) {
		byte[] buffer = textArea.getText().getBytes();
		File tempFile = null;

		if (currentFile != null) {
			tempFile = new File(currentFile.getAbsolutePath());
		}

		if (saveAs || currentFile == null) {
			try {
				tempFile = Dialogs.selectFile(
						FileExtensionFilters.getJSFileFilter(),
						FileSelectionMode.SAVE);
				if (tempFile == null) {
					return false;
				}

				if (FileIO.getFileExtension(tempFile.getName()) == null) {
					tempFile = new File(tempFile.getAbsolutePath() + ".js");
				}

				if (tempFile.exists()) {
					String message = String.format(
							rb.getString("warning.file.exists"),
							tempFile.getAbsolutePath());
					if (JOptionPane.showConfirmDialog(null, message, null,
							JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
						return false;
					}
				}
			} catch (Exception ex) {
				return false;
			}
		}

		try {
			FileIO.save(tempFile, buffer);
		} catch (IOException ex) {
			String message = String.format(rb.getString("error.file.save"),
					ex.getMessage());
			Dialogs.showError(rb.getString("error.title"), message);
			return false;
		}
		setCurrentFile(tempFile);
		return true;
	}

	private boolean openFile(String fileName) {
		if (isFileModified()) {
			if (Dialogs.showSavePrompt(rb.getString("prompt.save.changes"))) {
				if (!saveFile(false)) {
					return false;
				}
			}
		}

		File f = null;
		if (fileName == null) {
			try {
				f = Dialogs.selectFile(FileExtensionFilters.getJSFileFilter(),
						FileSelectionMode.OPEN);
				if (f == null) {
					return false;
				}
			} catch (Exception ex) {
				return false;
			}
		} else {
			f = new File(fileName);
		}

		if (!f.exists()) {
			String message = String.format(
					rb.getString("error.file.not.found"), f.getAbsolutePath());
			Dialogs.showError(rb.getString("error.title"), message);
			return false;
		}

		try {
			byte[] buffer = FileIO.read(f);
			textArea.setText(new String(buffer));
		} catch (IOException ex) {
			String message = String.format(rb.getString(f.getAbsolutePath()),
					ex.getMessage());
			Dialogs.showError(rb.getString("error.title"), message);
			return false;
		}

		setCurrentFile(f);
		return true;
	}

	private void refreshCanvas() {
		try {
			canvas.reset();
			se.eval(textArea.getText());
			drawingPanel.setPreferredSize(new Dimension(
					(int) canvas.getWidth(), (int) canvas.getHeight()));
			drawingPanel.setImage(canvas.getImage());
			drawingPanel.repaint();
			String labelText = String.format(
					rb.getString("label.status.canvas.size"),
					canvas.getWidth(), canvas.getHeight());
			statusLabel.setText(labelText);
			miExportImage.setEnabled(true);
		} catch (Exception ex) {
			miExportImage.setEnabled(false);
			String exceptionMessage = null;
			if (ex.getCause() != null) {
				exceptionMessage = ex.getCause().getMessage();
			} else {
				exceptionMessage = ex.getMessage();
			}
			String message = String.format(rb.getString("error.script"),
					exceptionMessage);
			log.warning(message);
			Dialogs.showError(rb.getString("error.title"), message);
		}
	}

	private void loadPreferences() {
		Preferences prefs = Preferences.userNodeForPackage(this.getClass());
		top = prefs.getInt("window.top", 100);
		left = prefs.getInt("window.left", 100);
		width = prefs.getInt("window.width", 640);
		height = prefs.getInt("window.height", 480);
		state = prefs.getInt("window.state", 0);
		String r = prefs.get("recent.files", null);
		recentFiles = new RecentFiles(RECENT_FILE_COUNT);
		if (r != null && r.length() > 0) {
			String[] ra = r.split(";");
			for (int i = ra.length - 1; i >= 0; i--) {
				recentFiles.put(new File(ra[i]));
			}
		}
	}

	private void savePreferences() {
		Preferences prefs = Preferences.userNodeForPackage(this.getClass());
		prefs.putInt("window.state", state);
		if (state == JFrame.MAXIMIZED_BOTH) {
			top = 100;
			left = 100;
			width = 640;
			height = 480;
		}

		prefs.putInt("window.top", top);
		prefs.putInt("window.left", left);
		prefs.putInt("window.width", width);
		prefs.putInt("window.height", height);

		String rf = "";

		for (File f : recentFiles.getRecentFiles()) {
			if (!rf.equals("")) {
				rf += ";";
			}
			rf += f.getAbsolutePath();
		}

		prefs.put("recent.files", rf);
	}

	private void refreshRecentFiles() {
		log.fine("refreshRecentFiles()");
		menuRecentFiles.removeAll();
		if (recentFiles.getRecentFiles().size() > 0) {
			for (File f : recentFiles.getRecentFiles()) {
				TaggedMenuItem mi = new TaggedMenuItem();
				mi.setTag(f);
				mi.setText(f.getName());
				mi.setToolTipText(f.getAbsolutePath());
				mi.addActionListener(menuActionListener);
				menuRecentFiles.add(mi);
			}
		}
	}

	private void exportImage() {
		File imageFile = Dialogs.selectFile(
				FileExtensionFilters.getPNGFilter(), FileSelectionMode.SAVE);

		if (imageFile == null) {
			return;
		}
		try {
			if (FileIO.getFileExtension(imageFile.getName()) == null) {
				imageFile = new File(imageFile.getAbsolutePath() + ".png");
			}

			if (imageFile.exists()) {
				String message = String.format(
						rb.getString("warning.file.exists"),
						imageFile.getAbsolutePath());
				if (JOptionPane.showConfirmDialog(null, message, null,
						JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
					return;
				}
			}

			log.fine("Exporting image to: " + imageFile.getAbsolutePath());
			ImageIO.write(canvas.getImage(), "png", imageFile);
		} catch (IOException ex) {
			log.warning("exportImage(): " + ex.getMessage());
			String message = String.format(rb.getString("error.image.export"),
					ex.getMessage());
			Dialogs.showError(rb.getString("error.title"), message);
		}
	}

	private void launchBrowser() {
		try {
			Desktop deskTop = Desktop.getDesktop();
			deskTop.browse(new URI(rb.getString("help.url")));
		} catch (Exception ex) {
			Dialogs.showError(rb.getString("error.title"), ex.getMessage());
		}

	}

	class MenuActionListener implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent event) {
			log.fine("Menu Action: " + event.getActionCommand());
			if (event.getActionCommand()
					.equals(rb.getString("menu.file.close"))) {
				WindowEvent closingEvent = new WindowEvent(MainWindow.this,
						WindowEvent.WINDOW_CLOSING);
				Toolkit.getDefaultToolkit().getSystemEventQueue()
						.postEvent(closingEvent);
			} else if (event.getActionCommand().equals(
					rb.getString("menu.file.new"))) {
				log.fine("New File: " + newFile());
			}

			else if (event.getActionCommand().equals(
					rb.getString("menu.file.open"))) {
				log.fine("Open File: " + openFile(null));
			}

			else if (event.getActionCommand().equals(
					rb.getString("menu.file.save"))) {
				saveFile(false);
			}

			else if (event.getActionCommand().equals(
					rb.getString("menu.file.saveas"))) {
				saveFile(true);
			}

			else if (event.getActionCommand().equals(
					rb.getString("menu.canvas.refresh"))) {
				refreshCanvas();
			}

			else if (event.getActionCommand().equals(
					rb.getString("menu.canvas.export"))) {
				exportImage();
			}

			else if (event.getActionCommand().equals(
					rb.getString("menu.help.online"))) {
				launchBrowser();
			}

			else if (TaggedMenuItem.class.isInstance(event.getSource())) {
				TaggedMenuItem mi = (TaggedMenuItem) event.getSource();
				openFile(((File) mi.getTag()).getAbsolutePath());
			}
		}
	}

	class TextAreaListener implements KeyListener {

		@Override
		public void keyPressed(KeyEvent event) {

		}

		@Override
		public void keyReleased(KeyEvent event) {
			if (!textArea.getText().equals(jsCode)) {
				setFileModified(true);
			}
		}

		@Override
		public void keyTyped(KeyEvent event) {

		}

	}

	class MainWindowListener implements WindowStateListener, WindowListener,
			WindowFocusListener, ComponentListener {

		@Override
		public void windowStateChanged(WindowEvent e) {
			log.fine("New state: " + e.getNewState());
			state = e.getNewState();
		}

		@Override
		public void windowActivated(WindowEvent e) {

		}

		@Override
		public void windowClosed(WindowEvent e) {

		}

		@Override
		public void windowClosing(WindowEvent e) {
			log.fine("Window Closing");
			savePreferences();
			if (isFileModified()) {
				if (Dialogs.showSavePrompt(rb.getString("prompt.save.changes"))) {
					if (!saveFile(false)) {
						return;
					}
					log.fine("File saved during close");
				} else {
					log.fine("File changes abandoned by user during close");
				}
			}
			System.exit(0);
		}

		@Override
		public void windowDeactivated(WindowEvent e) {

		}

		@Override
		public void windowDeiconified(WindowEvent e) {

		}

		@Override
		public void windowIconified(WindowEvent e) {

		}

		@Override
		public void windowOpened(WindowEvent e) {

		}

		@Override
		public void windowGainedFocus(WindowEvent e) {

		}

		@Override
		public void windowLostFocus(WindowEvent e) {

		}

		@Override
		public void componentHidden(ComponentEvent e) {

		}

		@Override
		public void componentMoved(ComponentEvent e) {
			width = getWidth();
			height = getHeight();
			top = (int) getBounds().getY();
			left = (int) getBounds().getX();
		}

		@Override
		public void componentResized(ComponentEvent e) {

		}

		@Override
		public void componentShown(ComponentEvent e) {

		}

	}

}
